from flask import (
    render_template,
    request,
    Flask,
    session,
    send_from_directory,
    jsonify,
    make_response,
)
import json
import datetime
import os
import requests
from verify import generatePhiZoneToken
from queue import Queue, Empty
from utils import getNow, getUUID


app = Flask(__name__)
app.config["SECRET_KEY"] = os.urandom(24)
app.config["PERMANENT_SESSION_LIFETIME"] = datetime.timedelta(days=7)
ver = "0.2.24"

rooms = {}


def genEvt(roomId, evt):
    room = rooms[roomId]
    evt["time"] = getNow()
    room["evt"].append(evt)
    for userId in room["players"]:
        player = room["players"][userId]
        if player["exited"] == 0:
            room["queues"][userId].put(1)


def createPlayer(access_token):
    url = "https://api.phi.zone/user_detail/"
    headers = {
        "Accept-Language": "zh-Hans",
        "User-Agent": "PhiZoneRegularAccess",
        "Authorization": "Bearer %s" % access_token,
    }
    t = requests.request("GET", url, headers=headers)
    res = json.loads(t.text)

    userId = str(res["id"])
    session["userId"] = userId
    session.permanent = True

    return {
        "id": userId,
        "name": res["username"],
        "avatar": res["avatar"],
        "scoreTotal": 0,
        "scoreAvg": 0,
        "accAvg": 0,
        "playRecord": [],
        "exited": 0,
    }


def getRoomInfoRaw(roomId):
    extractKeys = [
        "id",
        "stage",
        "playerNumber",
        "players",
        "owner",
        "playRound",
        "playRounds",
        "closed",
        "blackList",
    ]
    return getRoomInfo(roomId, extractKeys)


def getRoomInfoBasic(roomId):
    extractKeys = [
        "id",
        "stage",
        "playerNumber",
        "players",
        "owner",
        "closed",
        "blackList",
    ]
    return getRoomInfo(roomId, extractKeys)


def getRoomInfo(roomId, extractKeys):
    room = rooms[roomId]
    tempinfo = {}
    for k in extractKeys:
        tempinfo[k] = room[k]
    return tempinfo


def checkAutoStageChange(roomId):
    room = rooms[roomId]
    if (
        room["stage"] == 3
        and len(room["playRounds"][room["playRound"]]["scores"]) >= room["playerNumber"]
    ):
        room["stage"] = 4
        room["playRounds"][room["playRound"]]["finished"] = True
        genEvt(
            roomId,
            {
                "type": "allScoreUploaded",
                "msg": "所有玩家已经完成分数上传",
                "extraInfo": room["playRounds"][room["playRound"]],
            },
        )
    if (
        room["stage"] == 5
        and len(room["playRounds"][room["playRound"]]["loaded"]) >= room["playerNumber"]
    ):
        room["stage"] = 2
        genEvt(
            roomId,
            {
                "type": "allLoadFinish",
                "msg": "所有玩家已经完成谱面加载，游戏可以开始",
            },
        )


# phizone
@app.route("/api/pzEncrypted/play/<ct>/<cfg>/<access>")
def pzPlayChart(ct, cfg, access):
    url = "https://api.phi.zone/player/play/?chart=%s&token=%s&config=%s" % (
        ct,
        generatePhiZoneToken("PLCHRT"),
        cfg,
    )
    headers = {
        "Accept-Language": "zh-Hans",
        "User-Agent": "PhiZoneRegularAccess",
        "Authorization": "Bearer %s" % access,
    }
    t = requests.request("GET", url, headers=headers)
    res = make_response(t.text, 200)
    res.headers["Content-Type"] = "application/json"
    return res


@app.route("/api/pzEncrypted/record/<access>", methods=["POST"])
def pzRecord(access):
    req = json.loads(request.data)

    url = "https://api.phi.zone/records/"

    headers = {
        "Accept-Language": "zh-Hans",
        "User-Agent": "PhiZoneRegularAccess",
        "Authorization": "Bearer %s" % access,
    }

    req["token"] = generatePhiZoneToken("UPLRSL")
    req["app"] = 2

    t = requests.request("POST", url, headers=headers, data=req)

    res = make_response(t.text, 200)
    res.headers["Content-Type"] = "application/json"
    return res


# request
@app.route("/api/multi/requestRoom/<roomId>")  # 查询房间状态
def requstRoom(roomId):
    if roomId in rooms:
        if not rooms[roomId]["closed"]:
            if not rooms[roomId]["stage"] == 0:
                return jsonify(code=-1, msg="该房间比赛已开始")
            else:
                return jsonify(code=0, msg="该房间可以加入")
    return jsonify(code=-2, msg="房间不存在，您可以创建房间")


@app.route("/api/multi/createRoom/<roomId>", methods=["POST"])  # 创建房间
def createRoom(roomId):
    req = json.loads(request.data)
    if not roomId in rooms or rooms[roomId]["closed"]:
        thisplayer = createPlayer(req["access_token"])
        rooms[roomId] = {
            "id": roomId,
            "stage": 0,
            "playerNumber": 1,
            "players": {thisplayer["id"]: thisplayer},
            "owner": thisplayer["id"],
            "evt": [
                {
                    "type": "create",
                    "msg": "{} 创建了 {} 房间".format(thisplayer["name"], roomId),
                    "time": getNow(),
                }
            ],
            "playRound": 0,
            "playRounds": [],
            "blackList": [],
            "closed": False,
            "queues": {thisplayer["id"]: Queue()},
        }
        return jsonify(code=0, selfUser=thisplayer, selfRoom=getRoomInfoRaw(roomId))
    else:
        return jsonify(code=-1, msg="该房间ID已被占用")


@app.route("/api/multi/joinRoom/<roomId>", methods=["POST"])  # 加入房间
def joinRoom(roomId):
    req = json.loads(request.data)
    if roomId in rooms:
        thisplayer = createPlayer(req["access_token"])
        if thisplayer["id"] in rooms[roomId]["players"]:
            return jsonify(code=-2, msg="该账号已加入此房间，无法加入")
        if thisplayer["id"] in rooms[roomId]["blackList"]:
            return jsonify(code=-2, msg="您已经被移出该房间，无法加入")
        rooms[roomId]["playerNumber"] += 1
        rooms[roomId]["players"][thisplayer["id"]] = thisplayer
        rooms[roomId]["queues"][thisplayer["id"]] = Queue()
        genEvt(
            roomId,
            {
                "type": "join",
                "msg": "{} 加入了房间".format(thisplayer["name"]),
                "extraInfo": thisplayer,
            },
        )
        return jsonify(code=0, selfUser=thisplayer, selfRoom=getRoomInfoRaw(roomId))
    else:
        return jsonify(code=-1, msg="该房间不存在")


@app.route("/api/multi/getRoomInfo/<roomId>")
def getRoomInfoApi(roomId):
    return jsonify(getRoomInfoRaw(roomId))


@app.route("/api/multi/getRoomInfoBasic/<roomId>")
def getRoomInfoBasicApi(roomId):
    return jsonify(getRoomInfoBasic(roomId))


@app.route("/api/multi/lockRoom/<roomId>")  # 锁定房间
def lockRoom(roomId):
    userId = session.get("userId")
    if not userId or userId != rooms[roomId]["owner"]:
        return "Access denied"
    rooms[roomId]["stage"] = 1
    genEvt(
        roomId,
        {
            "type": "lock",
            "msg": "房间玩家名单锁定，游戏即将开始",
        },
    )
    return jsonify(code=0)


@app.route("/api/multi/kickPlayer/<roomId>/<targetId>")
def kickPlayer(roomId, targetId):
    userId = session.get("userId")
    if not userId:
        return "Access denied"

    room = rooms[roomId]
    player = room["players"][userId]

    if userId != targetId and userId != room["owner"]:
        return "Access denied"

    room["playerNumber"] -= 1

    if targetId == room["owner"]:
        genEvt(
            roomId,
            {
                "type": "close",
                "msg": "房间关闭",
            },
        )
        room["closed"] = True
        return jsonify(getRoomInfoRaw(roomId))
    else:
        exitType = 0
        if userId == targetId:
            exitType = 1
        else:
            room["blackList"].append(targetId)
            exitType = 2

        genEvt(
            roomId,
            {
                "type": "exit",
                "msg": "{} 退出房间".format(room["players"][targetId]["name"]),
                "extraInfo": {"id": targetId, "type": exitType},
            },
        )

        if room["stage"] == 0:  # 准备阶段，直接删除玩家
            del room["players"][targetId]
        else:  # 其他阶段，标记玩家为退出
            player["exited"] = exitType

        checkAutoStageChange(roomId)
    if userId == targetId:
        return jsonify(getRoomInfoRaw(roomId))
    else:
        return jsonify(code=0)


@app.route("/api/multi/syncChartInfo/<roomId>/<t>", methods=["POST"])
def syncChartInfo(roomId, t):
    userId = session.get("userId")
    room = rooms[roomId]
    player = room["players"][userId]

    if not userId or userId != room["owner"]:
        return "Access denied"

    req = json.loads(request.data)
    genEvt(
        roomId,
        {
            "type": "loadChart",
            "msg": "谱面 {} {} Lv.{} 已被选定".format(
                req["songData"]["name"],
                req["chartData"]["level"],
                req["chartData"]["difficulty"],
            ),
            "extraInfo": req,
        },
    )
    room["playRounds"].append(
        {
            "scores": {},
            "loaded": [],
            "chartInfo": req,
            "n": room["playRound"],
        }
    )
    room["stage"] = 5
    return jsonify(code=0)


@app.route("/api/multi/loadChartFinish/<roomId>/<t>")
def loadChartFinish(roomId, t):
    userId = session.get("userId")
    room = rooms[roomId]
    if not userId:
        return "Access denied"
    player = room["players"][userId]
    currentRound = room["playRounds"][room["playRound"]]

    if not userId in room["playRounds"][room["playRound"]]["loaded"]:
        room["playRounds"][room["playRound"]]["loaded"].append(userId)
        genEvt(
            roomId,
            {
                "type": "chartLoadFinish",
                "msg": "玩家 {} 已完成谱面加载，加载人数 {}/{}".format(
                    player["name"], len(currentRound["loaded"]), room["playerNumber"]
                ),
            },
        )
        checkAutoStageChange(roomId)
    return jsonify(code=0)


@app.route("/api/multi/startGamePlay/<roomId>")
def startGamePlay(roomId):
    userId = session.get("userId")
    if not userId or userId != rooms[roomId]["owner"]:
        return "Access denied"

    rooms[roomId]["stage"] = 3
    genEvt(
        roomId,
        {
            "type": "gameStart",
            "msg": "游戏开始",
        },
    )
    return jsonify(code=0)


@app.route("/api/multi/nextTrack/<roomId>")
def nextTrack(roomId):
    userId = session.get("userId")
    if not userId or userId != rooms[roomId]["owner"]:
        return "Access denied"

    rooms[roomId]["stage"] = 1
    rooms[roomId]["playRound"] += 1
    genEvt(
        roomId,
        {
            "type": "nextTrack",
            "msg": "下一轮游戏开始",
        },
    )
    return jsonify(code=0)


@app.route("/api/multi/getRoomEvt/<roomId>/<lastEvt>")
def getRoomEvt(roomId, lastEvt):
    userId = session.get("userId")
    if not userId:
        return "Access denied"

    lastEvt = int(lastEvt)
    room = rooms[roomId]
    if len(room["evt"]) > lastEvt:
        evtsToSend = room["evt"][lastEvt:]
        return jsonify(evtsToSend)
    else:
        try:
            q = room["queues"][userId].get(timeout=10)
            evtsToSend = room["evt"][lastEvt:]
            return jsonify(evtsToSend)
        except Empty:
            return jsonify([])


@app.route("/api/multi/uploadScoreInfo/<roomId>", methods=["POST"])
def uploadScoreInfo(roomId):
    userId = session.get("userId")
    if not userId:
        return "Access denied"
    req = json.loads(request.data)
    room = rooms[roomId]
    player = room["players"][userId]
    currentRound = room["playRounds"][room["playRound"]]
    scores = currentRound["scores"]

    # 更新房间数据
    req.update({"name": player["name"]})
    scores[userId] = req

    # 更新玩家数据
    player["playRecord"].append(req)
    scoresTotal = 0
    keysPerfect = 0
    keysGood = 0
    keysTotal = 0
    for item in player["playRecord"]:
        keysPerfect += item["perfect"]
        keysGood += item["good"]
        keysTotal += item["all"]
        scoresTotal += item["scoreNum"]
    player["scoreTotal"] = scoresTotal
    player["scoreAvg"] = scoresTotal / len(player["playRecord"])
    player["accAvg"] = keysPerfect / keysTotal + keysGood / keysTotal * 0.65

    genEvt(
        roomId,
        {
            "type": "sbScoreUploaded",
            "msg": "玩家 {} 已完成分数上传，上传人数 {}/{}".format(
                player["name"], len(scores), room["playerNumber"]
            ),
            "extraInfo": player,
        },
    )

    checkAutoStageChange(roomId)
    return jsonify(code=0)


@app.route("/api/multi/getLatestVersion")
def getLatestVersion():
    return jsonify(ver=ver)


# bot api
@app.route("/api/multi/bot/getOnlineRooms")
def getOnlineRoomsBot():
    ra = []
    for roomId in rooms:
        if not rooms[roomId]["closed"] and rooms[roomId]["stage"] == 0:
            room = rooms[roomId]
            ra.append(
                {
                    "id": roomId,
                    "owner": room["players"][room["owner"]]["name"],
                    "playerNumber": room["playerNumber"],
                }
            )
    return jsonify(ra)


# 静态资源
@app.route("/favicon.ico")
def favicon():
    return send_from_directory(
        os.path.join(app.root_path, "static/src/core"),
        "favicon.ico",
        mimetype="image/vnd.microsoft.icon",
    )


@app.route("/sw.v2.js")
def swv2():
    return send_from_directory(
        os.path.join(app.root_path, "static"), "sw.v2.js", mimetype="text/javascript"
    )


@app.route("/static/<name>.js")
def scriptsjs(name):
    return send_from_directory(
        os.path.join(app.root_path, "static"), name + ".js", mimetype="text/javascript"
    )


@app.route("/static/components/<dirx>/<name>.js")
def compjs(dirx, name):
    return send_from_directory(
        os.path.join(app.root_path, "static/components/" + dirx),
        name + ".js",
        mimetype="text/javascript",
    )


@app.route("/static/utils/js/<name>")
def comjs(name):
    return send_from_directory(
        os.path.join(app.root_path, "static/utils/js"), name, mimetype="text/javascript"
    )


@app.route("/fix/")
def fixCache():
    return r"<script>caches.keys().then(kl=>{kl.forEach(e=>caches.delete(e))})</script>"


@app.route("/")
def mainx():
    return render_template("mainplay.html", ver=ver)


if __name__ == "__main__":
    app.run(host="0.0.0.0", port=8081, debug=True, threaded=True)
